/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#ifdef HAVE_ILMIMF

#include <OpenEXR/ImfHeader.h>
#include <OpenEXR/ImfInputFile.h>
#include <OpenEXR/ImfOutputFile.h>
#include <OpenEXR/ImfChannelList.h>
#include <OpenEXR/ImfStandardAttributes.h>
#include <OpenEXR/half.h>
#include "grfmt_exr.hpp"

#if defined _MSC_VER && _MSC_VER >= 1200
#pragma comment(lib, "Half.lib")
#pragma comment(lib, "Iex.lib")
#pragma comment(lib, "IlmImf.lib")
#pragma comment(lib, "IlmThread.lib")
#pragma comment(lib, "Imath.lib")

#undef UINT
#define UINT ((Imf::PixelType)0)
#undef HALF
#define HALF ((Imf::PixelType)1)
#undef FLOAT
#define FLOAT ((Imf::PixelType)2)
#undef uint
#define uint unsigned

#endif

namespace cv {

/////////////////////// ExrDecoder ///////////////////

ExrDecoder::ExrDecoder() {
	m_signature = "\x76\x2f\x31\x01";
	m_file = new InputFile( filename );
	m_red = m_green = m_blue = 0;
}


ExrDecoder::~ExrDecoder() {
	close();
}


void  ExrDecoder::close() {
	if ( m_file ) {
		delete m_file;
		m_file = 0;
	}
}

bool  ExrDecoder::readHeader() {
	bool result = false;

	if ( !m_file ) { // probably paranoid
		return false;
	}

	m_datawindow = m_file->header().dataWindow();
	m_width = m_datawindow.max.x - m_datawindow.min.x + 1;
	m_height = m_datawindow.max.y - m_datawindow.min.y + 1;

	// the type HALF is converted to 32 bit float
	// and the other types supported by OpenEXR are 32 bit anyway
	m_bit_depth = 32;

	if ( hasChromaticities( m_file->header() )) {
		m_chroma = chromaticities( m_file->header() );
	}

	const ChannelList& channels = m_file->header().channels();
	m_red = channels.findChannel( "R" );
	m_green = channels.findChannel( "G" );
	m_blue = channels.findChannel( "B" );
	if ( m_red || m_green || m_blue ) {
		m_iscolor = true;
		m_ischroma = false;
		result = true;
	} else {
		m_green = channels.findChannel( "Y" );
		if ( m_green ) {
			m_ischroma = true;
			m_red = channels.findChannel( "RY" );
			m_blue = channels.findChannel( "BY" );
			m_iscolor = (m_blue || m_red);
			result = true;
		} else {
			result = false;
		}
	}

	if ( result ) {
		int uintcnt = 0;
		int chcnt = 0;
		if ( m_red ) {
			chcnt++;
			uintcnt += ( m_red->type == UINT );
		}
		if ( m_green ) {
			chcnt++;
			uintcnt += ( m_green->type == UINT );
		}
		if ( m_blue ) {
			chcnt++;
			uintcnt += ( m_blue->type == UINT );
		}
		m_type = (chcnt == uintcnt) ? UINT : FLOAT;

		m_isfloat = (m_type == FLOAT);
	}

	if ( !result ) {
		Close();
	}

	return result;
}


bool  ExrDecoder::readData( Mat& img ) {
	bool color = img.channels() > 1;
	uchar* data = img.data;
	int step = img.step;
	bool justcopy = m_native_depth;
	bool chromatorgb = false;
	bool rgbtogray = false;
	bool result = true;
	FrameBuffer frame;
	int xsample[3] = {1, 1, 1};
	char* buffer;
	int xstep;
	int ystep;

	xstep = m_native_depth ? 4 : 1;

	if ( !m_native_depth || (!color && m_iscolor )) {
		buffer = (char*)new float[ m_width * 3 ];
		ystep = 0;
	} else {
		buffer = (char*)data;
		ystep = step;
	}

	if ( m_ischroma ) {
		if ( color ) {
			if ( m_iscolor ) {
				if ( m_blue ) {
					frame.insert( "BY", Slice( m_type,
											   buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
											   12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 ));
					xsample[0] = m_blue->ySampling;
				}
				if ( m_green ) {
					frame.insert( "Y", Slice( m_type,
											  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
											  12, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
					xsample[1] = m_green->ySampling;
				}
				if ( m_red ) {
					frame.insert( "RY", Slice( m_type,
											   buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
											   12, ystep, m_red->xSampling, m_red->ySampling, 0.0 ));
					xsample[2] = m_red->ySampling;
				}
				chromatorgb = true;
			} else {
				frame.insert( "Y", Slice( m_type,
										  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
										  12, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
				frame.insert( "Y", Slice( m_type,
										  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
										  12, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
				frame.insert( "Y", Slice( m_type,
										  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
										  12, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
				xsample[0] = m_green->ySampling;
				xsample[1] = m_green->ySampling;
				xsample[2] = m_green->ySampling;
			}
		} else {
			frame.insert( "Y", Slice( m_type,
									  buffer - m_datawindow.min.x * 4 - m_datawindow.min.y * ystep,
									  4, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
			xsample[0] = m_green->ySampling;
		}
	} else {
		if ( m_blue ) {
			frame.insert( "B", Slice( m_type,
									  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
									  12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 ));
			xsample[0] = m_blue->ySampling;
		}
		if ( m_green ) {
			frame.insert( "G", Slice( m_type,
									  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
									  12, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
			xsample[1] = m_green->ySampling;
		}
		if ( m_red ) {
			frame.insert( "R", Slice( m_type,
									  buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
									  12, ystep, m_red->xSampling, m_red->ySampling, 0.0 ));
			xsample[2] = m_red->ySampling;
		}
		if (color == 0) {
			rgbtogray = true;
			justcopy = false;
		}
	}

	m_file->setFrameBuffer( frame );
	if ( justcopy ) {
		m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y );

		if ( color ) {
			if ( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) {
				UpSample( data, 3, step / xstep, xsample[0], m_blue->ySampling );
			}
			if ( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) {
				UpSample( data + xstep, 3, step / xstep, xsample[1], m_green->ySampling );
			}
			if ( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) {
				UpSample( data + 2 * xstep, 3, step / xstep, xsample[2], m_red->ySampling );
			}
		} else if ( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) {
			UpSample( data, 1, step / xstep, xsample[0], m_green->ySampling );
		}
	} else {
		uchar* out = data;
		int x, y;
		for ( y = m_datawindow.min.y; y <= m_datawindow.max.y; y++ ) {
			m_file->readPixels( y, y );

			if ( rgbtogray ) {
				if ( xsample[0] != 1 ) {
					UpSampleX( (float*)buffer, 3, xsample[0] );
				}
				if ( xsample[1] != 1 ) {
					UpSampleX( (float*)buffer + 4, 3, xsample[1] );
				}
				if ( xsample[2] != 1 ) {
					UpSampleX( (float*)buffer + 8, 3, xsample[2] );
				}

				RGBToGray( (float*)buffer, (float*)out );
			} else {
				if ( xsample[0] != 1 ) {
					UpSampleX( (float*)buffer, 3, xsample[0] );
				}
				if ( xsample[1] != 1 ) {
					UpSampleX( (float*)(buffer + 4), 3, xsample[1] );
				}
				if ( xsample[2] != 1 ) {
					UpSampleX( (float*)(buffer + 8), 3, xsample[2] );
				}

				if ( chromatorgb ) {
					ChromaToBGR( (float*)buffer, 1, step );
				}

				if ( m_type == FLOAT ) {
					float* fi = (float*)buffer;
					for ( x = 0; x < m_width * 3; x++) {
						int t = cvRound(fi[x] * 5);
						out[x] = CV_CAST_8U(t);
					}
				} else {
					uint* ui = (uint*)buffer;
					for ( x = 0; x < m_width * 3; x++) {
						uint t = ui[x];
						out[x] = CV_CAST_8U(t);
					}
				}
			}

			out += step;
		}
		if ( color ) {
			if ( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) {
				UpSampleY( data, 3, step / xstep, m_blue->ySampling );
			}
			if ( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) {
				UpSampleY( data + xstep, 3, step / xstep, m_green->ySampling );
			}
			if ( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) {
				UpSampleY( data + 2 * xstep, 3, step / xstep, m_red->ySampling );
			}
		} else if ( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) {
			UpSampleY( data, 1, step / xstep, m_green->ySampling );
		}
	}

	if ( chromatorgb ) {
		ChromaToBGR( (float*)data, m_height, step / xstep );
	}

	Close();

	return result;
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSample( uchar* data, int xstep, int ystep, int xsample, int ysample ) {
	for ( int y = (m_height - 1) / ysample, yre = m_height - ysample; y >= 0; y--, yre -= ysample ) {
		for ( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample ) {
			for ( int i = 0; i < ysample; i++ ) {
				for ( int n = 0; n < xsample; n++ ) {
					if ( !m_native_depth ) {
						data[(yre + i) * ystep + (xre + n) * xstep] = data[y * ystep + x * xstep];
					} else if ( m_type == FLOAT ) {
						((float*)data)[(yre + i) * ystep + (xre + n) * xstep] = ((float*)data)[y * ystep + x * xstep];
					} else {
						((uint*)data)[(yre + i) * ystep + (xre + n) * xstep] = ((uint*)data)[y * ystep + x * xstep];
					}
				}
			}
		}
	}
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSampleX( float* data, int xstep, int xsample ) {
	for ( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample ) {
		for ( int n = 0; n < xsample; n++ ) {
			if ( m_type == FLOAT ) {
				((float*)data)[(xre + n) * xstep] = ((float*)data)[x * xstep];
			} else {
				((uint*)data)[(xre + n) * xstep] = ((uint*)data)[x * xstep];
			}
		}
	}
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSampleY( uchar* data, int xstep, int ystep, int ysample ) {
	for ( int y = m_height - ysample, yre = m_height - ysample; y >= 0; y -= ysample, yre -= ysample ) {
		for ( int x = 0; x < m_width; x++ ) {
			for ( int i = 1; i < ysample; i++ ) {
				if ( !m_native_depth ) {
					data[(yre + i) * ystep + x* xstep] = data[y * ystep + x * xstep];
				} else if ( m_type == FLOAT ) {
					((float*)data)[(yre + i) * ystep + x* xstep] = ((float*)data)[y * ystep + x * xstep];
				} else {
					((uint*)data)[(yre + i) * ystep + x* xstep] = ((uint*)data)[y * ystep + x * xstep];
				}
			}
		}
	}
}

/**
// algorithm from ImfRgbaYca.cpp
 */
void  ExrDecoder::ChromaToBGR( float* data, int numlines, int step ) {
	int x, y, t;

	for ( y = 0; y < numlines; y++ ) {
		for ( x = 0; x < m_width; x++ ) {
			double b, Y, r;
			if ( !m_native_depth ) {
				b = ((uchar*)data)[y * step + x * 3];
				Y = ((uchar*)data)[y * step + x * 3 + 1];
				r = ((uchar*)data)[y * step + x * 3 + 2];
			} else if ( m_type == FLOAT ) {
				b = data[y * step + x * 3];
				Y = data[y * step + x * 3 + 1];
				r = data[y * step + x * 3 + 2];
			} else {
				b = ((uint*)data)[y * step + x * 3];
				Y = ((uint*)data)[y * step + x * 3 + 1];
				r = ((uint*)data)[y * step + x * 3 + 2];
			}
			r = (r + 1) * Y;
			b = (b + 1) * Y;
			Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1];

			if ( !m_native_depth ) {
				int t = cvRound(b);
				((uchar*)data)[y* step + x * 3] = CV_CAST_8U(t);
				t = cvRound(Y);
				((uchar*)data)[y* step + x * 3 + 1] = CV_CAST_8U(t);
				t = cvRound(r);
				((uchar*)data)[y* step + x * 3 + 2] = CV_CAST_8U(t);
			} else if ( m_type == FLOAT ) {
				data[y* step + x * 3] = (float)b;
				data[y* step + x * 3 + 1] = (float)Y;
				data[y* step + x * 3 + 2] = (float)r;
			} else {
				int t = cvRound(b);
				((uint*)data)[y* step + x * 3] = (uint)MAX(t, 0);
				t = cvRound(Y);
				((uint*)data)[y* step + x * 3 + 1] = (uint)MAX(t, 0);
				t = cvRound(r);
				((uint*)data)[y* step + x * 3 + 2] = (uint)MAX(t, 0);
			}
		}
	}
}


/**
// convert one row to gray
*/
void  ExrDecoder::RGBToGray( float* in, float* out ) {
	if ( m_type == FLOAT ) {
		if ( m_native_depth ) {
			for ( int i = 0, n = 0; i < m_width; i++, n += 3 ) {
				out[i] = in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0];
			}
		} else {
			uchar* o = (uchar*)out;
			for ( int i = 0, n = 0; i < m_width; i++, n += 3 ) {
				o[i] = (uchar) (in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0]);
			}
		}
	} else { // UINT
		if ( m_native_depth ) {
			uint* ui = (uint*)in;
			for ( int i = 0; i < m_width * 3; i++ ) {
				ui[i] -= 0x80000000;
			}
			int* si = (int*)in;
			for ( int i = 0, n = 0; i < m_width; i++, n += 3 ) {
				((int*)out)[i] = int(si[n] * m_chroma.blue[0] + si[n + 1] * m_chroma.green[0] + si[n + 2] * m_chroma.red[0]);
			}
		} else { // how to best convert float to uchar?
			uint* ui = (uint*)in;
			for ( int i = 0, n = 0; i < m_width; i++, n += 3 ) {
				((uchar*)out)[i] = uchar((ui[n] * m_chroma.blue[0] + ui[n + 1] * m_chroma.green[0] + ui[n + 2] * m_chroma.red[0]) * (256.0 / 4294967296.0));
			}
		}
	}
}

/////////////////////// ExrEncoder ///////////////////


ExrEncoder::ExrEncoder() {
	m_description = "OpenEXR Image files (*.exr)";
}


ExrEncoder::~ExrEncoder() {
}


bool  ExrEncoder::isFormatSupported( int depth ) const {
	return CV_MAT_DEPTH(depth) >= CV_8U && CV_MAT_DEPTH(depth) < CV_64F;
}


// TODO scale appropriately
bool  ExrEncoder::write( const Mat& img, const Vector<int>& ) {
	int width = img.cols, height = img.rows;
	int depth = img.depth(), channels = img.channels();
	bool result = false;
	bool issigned = depth == CV_8S || depth == CV_16S || depth == CV_32S;
	bool isfloat = depth == CV_32F || depth == CV_64F;
	depth = CV_ELEM_SIZE1(depth) * 8;
	uchar* data = img.data;
	int step = img.step;

	Header header( width, height );
	PixelType type;

	if (depth == 8) {
		type = HALF;
	} else if (isfloat) {
		type = FLOAT;
	} else {
		type = UINT;
	}

	if ( channels == 3 ) {
		header.channels().insert( "R", Channel( type ));
		header.channels().insert( "G", Channel( type ));
		header.channels().insert( "B", Channel( type ));
		//printf("bunt\n");
	} else {
		header.channels().insert( "Y", Channel( type ));
		//printf("gray\n");
	}

	OutputFile file( m_filename.c_str(), header );

	FrameBuffer frame;

	char* buffer;
	int bufferstep;
	int size;
	if ( type == FLOAT && depth == 32 ) {
		buffer = (char*)const_cast<uchar*>(data);
		bufferstep = step;
		size = 4;
	} else if ( depth > 16 || type == UINT ) {
		buffer = (char*)new uint[width * channels];
		bufferstep = 0;
		size = 4;
	} else {
		buffer = (char*)new half[width * channels];
		bufferstep = 0;
		size = 2;
	}

	//printf("depth %d %s\n", depth, types[type]);

	if ( channels == 3 ) {
		frame.insert( "B", Slice( type, buffer, size * 3, bufferstep ));
		frame.insert( "G", Slice( type, buffer + size, size * 3, bufferstep ));
		frame.insert( "R", Slice( type, buffer + size * 2, size * 3, bufferstep ));
	} else {
		frame.insert( "Y", Slice( type, buffer, size, bufferstep ));
	}

	file.setFrameBuffer( frame );

	int offset = issigned ? 1 << (depth - 1) : 0;

	result = true;
	if ( type == FLOAT && depth == 32 ) {
		try {
			file.writePixels( height );
		} catch (...) {
			result = false;
		}
	} else {
		//    int scale = 1 << (32 - depth);
		//    printf("scale %d\n", scale);
		for (int line = 0; line < height; line++) {
			if (type == UINT) {
				uint* buf = (uint*)buffer;  // FIXME 64-bit problems

				if ( depth <= 8 ) {
					for (int i = 0; i < width * channels; i++) {
						buf[i] = data[i] + offset;
					}
				} else if ( depth <= 16 ) {
					unsigned short* sd = (unsigned short*)data;
					for (int i = 0; i < width * channels; i++) {
						buf[i] = sd[i] + offset;
					}
				} else {
					int* sd = (int*)data;  // FIXME 64-bit problems
					for (int i = 0; i < width * channels; i++) {
						buf[i] = (uint) sd[i] + offset;
					}
				}
			} else {
				half* buf = (half*)buffer;

				if ( depth <= 8 ) {
					for (int i = 0; i < width * channels; i++) {
						buf[i] = data[i];
					}
				} else if ( depth <= 16 ) {
					unsigned short* sd = (unsigned short*)data;
					for (int i = 0; i < width * channels; i++) {
						buf[i] = sd[i];
					}
				}
			}
			try {
				file.writePixels( 1 );
			} catch (...) {
				result = false;
				break;
			}
			data += step;
		}
		delete buffer;
	}

	return result;
}

}

#endif

/* End of file. */
